/*
Authors: Eli Yucht 300388949 and Lior Fass 300069705
Project: Hamming
Description: Socket_tool class implementation
*/
#include "socket_tools.h"

/**
* Initialize winsock 2 (call to WSAStartup())
*
* Accepts:
* -------
*  None
*
* Returns:
* -------
*  1- if failed
   0 - if successed
*/

int Socket_tool::WinSock_Init()
{
	WSADATA wsaData;
	int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != NO_ERROR)
	{
		cerr << "Error at WSAStartup()\n";
		return 1;
	}
	return 0;
}

/*
* Create a passive TCP socket for listening (call to sock(), bind() and listen())
*
* Accepts:
* -------
*  char* name - "sender" or "receiver" string
	unsigned short port_number - the port number that should be bind to the socket

*
* Returns:
* -------
*  A descriptor to the passive socket if succeed or INVALID_SOCKET if failed
*/

SOCKET Socket_tool::Create_Socket_and_Listen(char* name, unsigned short port_number)
{
	SOCKET passive_socket = INVALID_SOCKET;
	passive_socket = socket(PF_INET, SOCK_STREAM, 0);
	if (passive_socket == INVALID_SOCKET)
	{
		cerr << "Error at socket() for " << name << "\nLast error: " << WSAGetLastError();
		return INVALID_SOCKET;
	}
	struct sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_addr.s_addr = INADDR_ANY;
	service.sin_port = htons(port_number);
	int bind_result = bind(passive_socket, (SOCKADDR*)&service, sizeof(service));

	if (bind_result == SOCKET_ERROR)
	{
		cerr << "Error at bind() for " << name << "\nLast error: " << WSAGetLastError();
		return INVALID_SOCKET;
	}
	if (listen(passive_socket, MAX_QUEUE) == SOCKET_ERROR)
	{
		cerr << "Error at listen() for " << name << "\nLast error: " << WSAGetLastError();
		return INVALID_SOCKET;
	}
	return passive_socket;
}


/**
* SendBuffer() uses a socket to send a buffer.
*
* Accepts:
* -------
* Buffer - the buffer containing the data to be sent.
* BytesToSend - the number of bytes from the Buffer to send.
* sd - the socket used for communication.
*
* Returns:
* -------
* TRNS_SUCCEEDED - if sending succeeded
* TRNS_FAILED - otherwise
*/

TransferResult_t Socket_tool::SendBuffer(const char* Buffer, int BytesToSend, SOCKET sd)
{
	const char* CurPlacePtr = Buffer;
	int BytesTransferred;
	int RemainingBytesToSend = BytesToSend;

	while (RemainingBytesToSend > 0)
	{
		/* send does not guarantee that the entire message is sent */
		BytesTransferred = send(sd, CurPlacePtr, RemainingBytesToSend, 0);
		if (BytesTransferred == SOCKET_ERROR)
		{
			printf("send() failed, error %d\n", WSAGetLastError());
			return TRNS_FAILED;
		}

		RemainingBytesToSend -= BytesTransferred;
		CurPlacePtr += BytesTransferred; // <ISP> pointer arithmetic
	}

	return TRNS_SUCCEEDED;
}

/*
* SendString() uses a socket to send a string.
* Str - the string to send.
* sd - the socket used for communication.
*
* Returns:
* -------
* TRNS_SUCCEEDED - if sending succeeded
* TRNS_FAILED - otherwise
*/

TransferResult_t Socket_tool::SendString(const char *Str, SOCKET sd)
{
	/* Send the the request to the server on socket sd */
	int TotalStringSizeInBytes;
	TransferResult_t SendRes;

	/* The request is sent in two parts. First the Length of the string (stored in
	an int variable ), then the string itself. */

	TotalStringSizeInBytes = (int)(strlen(Str) + 1); // terminating zero also sent	

	SendRes = SendBuffer(
		(const char *)(&TotalStringSizeInBytes),
		(int)(sizeof(TotalStringSizeInBytes)), // sizeof(int) == 4 bytes
		sd);

	if (SendRes != TRNS_SUCCEEDED) return SendRes;

	SendRes = SendBuffer(
		(const char *)(Str),
		(int)(TotalStringSizeInBytes),
		sd);

	return SendRes;
}

/*
* Create a connected TCP socket [for send() and recv() functions]
*
* Accepts:
* -------
*  char* channel_ip_addr - The ip address of the server
	unsigned short channel_port - The port number of the server to connect to it

*
* Returns:
* -------
*  A descriptor to the connected socket if succeed or INVALID_SOCKET if failed
*/

SOCKET Socket_tool::Connect_Channel(char* channel_ip_addr, unsigned short channel_port)
{
	if (WinSock_Init())
		return INVALID_SOCKET;

	SOCKET client_socket = socket(PF_INET, SOCK_STREAM, 0);
	if (client_socket == INVALID_SOCKET)
	{
		cerr << "Socket() failed: " << WSAGetLastError() << "\n";
		return INVALID_SOCKET;
	}
	struct sockaddr_in channel_addr;

	channel_addr.sin_family = AF_INET;
	channel_addr.sin_addr.s_addr = inet_addr(channel_ip_addr);
	channel_addr.sin_port = htons(channel_port);

	int status = connect(client_socket, (SOCKADDR*)&channel_addr, sizeof(channel_addr));

	if (status == SOCKET_ERROR)
	{
		cerr << "connect() failed: " << WSAGetLastError() << "\n";
		closesocket(client_socket);
		return INVALID_SOCKET;
	}
	return client_socket;

}

/*ReceiveBuffer() uses a socket to receive a Buffer (and stores it in OutputBuffer)
*
* Accepts:
* -------
* OutputBuffer - pointer to a buffer into which data will be written
* BytesToReceive - size in bytes of Output Buffer
* int *RemainingBytesToReceive - (use as output) a pointer to the remainig number of bytes to receive
* sd - the socket used for communication.
*
* Returns:
* -------
* TRNS_SUCCEEDED - if receiving succeeded
* TRNS_DISCONNECTED - if the socket was disconnected
* TRNS_FAILED - otherwise
*/


TransferResult_t Socket_tool::ReceiveBuffer(char* OutputBuffer, int BytesToReceive, int *RemainingBytesToReceive, SOCKET sd)
{
	char* CurPlacePtr = OutputBuffer;
	int BytesJustTransferred;
	*RemainingBytesToReceive = BytesToReceive;

	while (*RemainingBytesToReceive > 0)
	{
		/* recv does not guarantee that the entire message is received */
		BytesJustTransferred = recv(sd, CurPlacePtr, *RemainingBytesToReceive, 0);
		if (BytesJustTransferred == SOCKET_ERROR)
		{
			printf("recv() failed, error %d\n", WSAGetLastError());
			return TRNS_FAILED;
		}
		else if (BytesJustTransferred == 0)
			return TRNS_DISCONNECTED; // recv() returns zero if connection was gracefully disconnected.

		*RemainingBytesToReceive -= BytesJustTransferred;
		CurPlacePtr += BytesJustTransferred; // <ISP> pointer arithmetic
	}

	return TRNS_SUCCEEDED;
}

/**
* ReceiveString() uses a socket to receive a string, and stores it in dynamic memory.
*
* Accepts:
* -------
* OutputStrPtr - a pointer to a char-pointer that is initialized to NULL, as in:
*
*		char *Buffer = NULL;
*		ReceiveString( &Buffer, ___ );
*
* a dynamically allocated string will be created, and (*OutputStrPtr) will point to it.
*
* sd - the socket used for communication.
*
* Returns:
* -------
* TRNS_SUCCEEDED - if receiving and memory allocation succeeded
* TRNS_DISCONNECTED - if the socket was disconnected
* TRNS_FAILED - otherwise
*/


TransferResult_t Socket_tool::ReceiveString(char** OutputStrPtr, SOCKET sd)
{
	/* Recv a string on socket sd */
	int TotalStringSizeInBytes;
	TransferResult_t RecvRes;
	char* StrBuffer = NULL;
	int remaining_bytes_recv;

	if ((OutputStrPtr == NULL) || (*OutputStrPtr != NULL))
	{
		printf("The first input to ReceiveString() must be "
			"a pointer to a char pointer that is initialized to NULL. For example:\n"
			"\tchar* Buffer = NULL;\n"
			"\tReceiveString( &Buffer, ___ )\n");
		return TRNS_FAILED;
	}

	/* The request is received in two parts. First the Length of the string (stored in
	an int variable ), then the string itself. */

	RecvRes = ReceiveBuffer(
		(char *)(&TotalStringSizeInBytes),
		(int)(sizeof(TotalStringSizeInBytes)),// 4 bytes
		&remaining_bytes_recv,
		sd);

	if (RecvRes != TRNS_SUCCEEDED) return RecvRes;

	StrBuffer = (char*)malloc(TotalStringSizeInBytes * sizeof(char));

	if (StrBuffer == NULL)
		return TRNS_FAILED;

	RecvRes = ReceiveBuffer(
		(char *)(StrBuffer),
		(int)(TotalStringSizeInBytes),
		&remaining_bytes_recv,
		sd);

	if (RecvRes == TRNS_SUCCEEDED)
		*OutputStrPtr = StrBuffer;
	else
		free(StrBuffer);


	return RecvRes;
}
